# -*- coding: utf-8 -*-
"""
解析ERA5 NetCDF格式气象数据的示例脚本

此脚本展示了如何使用Python读取和分析ERA5 NetCDF格式的气象数据文件。
它包含了基本的数据读取、查看数据结构、提取变量以及简单的数据可视化功能。
采用面向对象设计，支持不同类型的ERA5数据格式（日降水量和小时降水量）。
"""
import datetime
import os
import re
import xarray as xr
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import pandas as pd
from abc import ABC, abstractmethod

# 设置matplotlib中文显示
try:
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
except Exception as e:
    print(f"警告: 无法设置中文显示，图表标签可能无法正确显示中文 {e}")


class ERA5Parser(ABC):
    """
    ERA5数据解析器的抽象基类
    定义了所有ERA5解析器必须实现的接口
    """

    def __init__(self, dataset, file_path=None):
        """
        初始化解析器

        参数:
            dataset (xarray.Dataset): 包含ERA5数据的xarray数据集
            file_path (str, 可选): 数据文件路径，用于提取年份和月份信息
        """
        self.dataset = dataset
        self.time_dim = self.get_time_dimension()
        self.file_path = file_path

        # 解析文件名中的年份和月份信息
        self.year = None
        self.month = None
        if file_path:
            self._parse_file_info(file_path)

    @abstractmethod
    def get_time_dimension(self):
        """
        获取时间维度名称

        返回:
            str: 时间维度的名称
        """
        pass

    def _parse_file_info(self, file_path):
        """
        从文件路径中解析年份和月份信息
        
        参数:
            file_path (str): 数据文件路径
        """
        # 获取文件名（不包含路径）
        filename = os.path.basename(file_path)

        # 尝试匹配日降水量文件格式 (ERA5.P.daily.1960.nc)
        daily_match = re.search(r'ERA5\.P\.daily\.(\d{4})\.nc', filename)
        if daily_match:
            self.year = int(daily_match.group(1))
            return

        # 尝试匹配小时降水量文件格式 (ERA5-P.hourly.1940.09.nc)
        hourly_match = re.search(r'ERA5-P\.hourly\.(\d{4})\.(\d{2})\.nc', filename)
        if hourly_match:
            self.year = int(hourly_match.group(1))
            self.month = int(hourly_match.group(2))
            return

        # 尝试更通用的模式
        year_match = re.search(r'\.(\d{4})\.|\.(\d{4})$', filename)
        if year_match:
            self.year = int(year_match.group(1) or year_match.group(2))

        # 尝试匹配月份
        month_match = re.search(r'\.(\d{2})\.|\.(\d{2})$', filename)
        if month_match and not month_match.group(0).endswith(str(self.year)):
            self.month = int(month_match.group(1) or month_match.group(2))

    def format_date(self, time_value):
        """
        根据解析器类型格式化日期
        
        参数:
            time_value: 时间值
            
        返回:
            str: 格式化的日期字符串
        """
        # 由子类实现
        pass

    def display_info(self):
        """
        显示数据集的基本信息
        """
        if self.dataset is None:
            print("没有可用的数据集")
            return

        print("\n数据集基本信息:")
        print("=" * 50)
        print(f"数据集维度: {self.dataset.dims}")
        print(f"时间维度: {self.time_dim}")
        print("\n变量列表:")
        for var_name, var in self.dataset.variables.items():
            print(f"  - {var_name}: {var.dims} {var.shape}")

        print("\n数据集属性:")
        for attr_name, attr_value in self.dataset.attrs.items():
            print(f"  - {attr_name}: {attr_value}")

    def extract_variable(self, variable_name):
        """
        从数据集中提取指定变量

        参数:
            variable_name (str): 要提取的变量名称

        返回:
            xarray.DataArray: 提取的变量数据数组
        """
        if self.dataset is None:
            print("没有可用的数据集")
            return None

        if variable_name not in self.dataset.variables:
            print(f"变量 '{variable_name}' 不在数据集中")
            print(f"可用变量: {list(self.dataset.variables.keys())}")
            return None

        return self.dataset[variable_name]

    def plot_spatial_data(self, data_array=None, variable_name=None, time_index=0, title=None):
        """
        绘制空间数据图

        参数:
            data_array (xarray.DataArray, 可选): 要绘制的数据数组，如果为None，则使用variable_name从dataset中提取
            variable_name (str, 可选): 要从dataset中提取的变量名称，仅当data_array为None时使用
            time_index (int): 要绘制的时间索引
            title (str): 图表标题
        """
        # 如果没有提供data_array，尝试从dataset中提取变量
        if data_array is None:
            if variable_name is not None:
                data_array = self.extract_variable(variable_name)
            elif self.dataset is not None and len(self.dataset.data_vars) > 0:
                # 如果没有指定变量名，使用第一个数据变量
                variable_name = list(self.dataset.data_vars)[0]
                data_array = self.extract_variable(variable_name)
                print(f"使用默认变量: {variable_name}")
            else:
                print("没有可用的数据，请提供data_array或variable_name")
                return

        if data_array is None:
            print("没有可用的数据")
            return

        # 获取数据切片和时间字符串
        data_slice, time_str = self._prepare_time_slice(data_array, time_index)

        # 创建图形
        plt.figure(figsize=(12, 8))

        # 绘制数据
        self._plot_spatial_data_impl(data_array, data_slice, time_str, title)

        plt.tight_layout()
        plt.show()

    def _prepare_time_slice(self, data_array, time_index):
        """
        准备时间切片数据

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            time_index (int): 要处理的时间索引

        返回:
            tuple: (data_slice, time_str) 数据切片和时间字符串
        """
        # 检查数据数组是否包含时间维度
        if self.time_dim in data_array.dims:
            # 选择特定时间点的数据
            if time_index >= len(data_array[self.time_dim]):
                print(f"时间索引超出范围，最大索引为 {len(data_array[self.time_dim]) - 1}")
                time_index = 0

            time_value = data_array[self.time_dim].values[time_index]
            data_slice = data_array.isel({self.time_dim: time_index})

            # 尝试将时间值转换为日期字符串
            try:
                time_str = pd.to_datetime(time_value).strftime('%Y-%m-%d %H:%M:%S')
            except:
                time_str = f"{self.time_dim}={time_value}"
        else:
            data_slice = data_array
            time_str = ""

        return data_slice, time_str

    def _plot_spatial_data_impl(self, data_array, data_slice, time_str, title):
        """
        实现空间数据绘制的具体逻辑

        参数:
            data_array (xarray.DataArray): 原始数据数组
            data_slice (xarray.DataArray): 时间切片后的数据数组
            time_str (str): 时间字符串
            title (str): 图表标题
        """
        # 检查是否有经纬度坐标
        lat_dim, lon_dim = self.get_spatial_dimensions()
        if all(dim in data_array.dims for dim in [lat_dim, lon_dim]):
            ax = plt.axes(projection=ccrs.PlateCarree())
            ax.coastlines()
            ax.gridlines(draw_labels=True)

            # 绘制数据
            im = data_slice.plot(ax=ax, transform=ccrs.PlateCarree(), cmap='viridis',
                                 add_colorbar=True)

            if title:
                plt.title(f"{title} - {time_str}")
            else:
                plt.title(f"{data_array.name} - {time_str}")
        else:
            # 对于没有经纬度的数据，需要检查数据维度并选择合适的绘图方法
            # 检查数据维度数量
            ndims = len(data_slice.dims)

            if ndims <= 1:
                # 对于一维数据，使用线图
                im = data_slice.plot.line()
            elif ndims == 2:
                # 对于二维数据，使用热图
                im = data_slice.plot.pcolormesh(cmap='viridis')
            else:
                # 对于多维数据，先转换为二维再绘图
                print(f"警告: 数据维度为{ndims}，将尝试转换为二维数据进行绘制")
                # 获取前两个维度
                dims = list(data_slice.dims)
                reduced_data = data_slice.mean(dim=[d for d in dims[2:]])
                im = reduced_data.plot.pcolormesh(cmap='viridis')

            if title:
                plt.title(f"{title} - {time_str}")
            else:
                plt.title(f"{data_array.name} - {time_str}")

    def plot_time_series(self, data_array=None, variable_name=None, lat=None, lon=None):
        """
        绘制时间序列数据

        参数:
            data_array (xarray.DataArray, 可选): 要绘制的数据数组，如果为None，则使用variable_name从dataset中提取
            variable_name (str, 可选): 要从dataset中提取的变量名称，仅当data_array为None时使用
            lat (float): 要提取的纬度点
            lon (float): 要提取的经度点
        """
        # 如果没有提供data_array，尝试从dataset中提取变量
        if data_array is None:
            if variable_name is not None:
                data_array = self.extract_variable(variable_name)
            elif self.dataset is not None and len(self.dataset.data_vars) > 0:
                # 如果没有指定变量名，使用第一个数据变量
                variable_name = list(self.dataset.data_vars)[0]
                data_array = self.extract_variable(variable_name)
                print(f"使用默认变量: {variable_name}")
            else:
                print("没有可用的数据，请提供data_array或variable_name")
                return

        if data_array is None:
            print("没有可用的数据")
            return

        # 检查数据数组是否包含时间维度
        if self.time_dim not in data_array.dims:
            print(f"数据不包含时间维度 {self.time_dim}，无法绘制时间序列")
            return

        # 准备数据点和位置字符串
        data_point, location_str = self._prepare_time_series_data(data_array, lat, lon)
        if data_point is None:
            return

        # 创建图形
        plt.figure(figsize=(12, 6))

        # 绘制时间序列
        self._plot_time_series_impl(data_array, data_point, location_str)

        plt.tight_layout()
        plt.show()

    def _prepare_time_series_data(self, data_array, lat=None, lon=None):
        """
        准备时间序列数据

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            lat (float): 要提取的纬度点
            lon (float): 要提取的经度点

        返回:
            tuple: (data_point, location_str) 数据点和位置字符串
        """
        lat_dim, lon_dim = self.get_spatial_dimensions()

        # 如果提供了经纬度，则提取该点的时间序列
        if lat is not None and lon is not None:
            if lat_dim in data_array.dims and lon_dim in data_array.dims:
                # 找到最接近的网格点
                data_point = data_array.sel({lat_dim: lat, lon_dim: lon}, method='nearest')
                location_str = f"(纬度: {data_point[lat_dim].values}, 经度: {data_point[lon_dim].values})"
            else:
                print(f"数据不包含经纬度维度 {lat_dim} 和 {lon_dim}，无法提取特定位置")
                return None, None
        else:
            # 如果没有提供经纬度，则计算空间平均
            spatial_dims = [dim for dim in data_array.dims if dim != self.time_dim]
            if spatial_dims:
                data_point = data_array.mean(dim=spatial_dims)
                location_str = "(空间平均)"
            else:
                data_point = data_array
                location_str = ""

        return data_point, location_str

    def _plot_time_series_impl(self, data_array, data_point, location_str):
        """
        实现时间序列绘制的具体逻辑

        参数:
            data_array (xarray.DataArray): 原始数据数组
            data_point (xarray.DataArray): 处理后的数据点
            location_str (str): 位置字符串
        """
        # 绘制时间序列
        data_point.plot()

        # 设置标题和标签
        plt.title(f"{data_array.name} 时间序列 {location_str}")
        plt.xlabel('时间')
        plt.ylabel(f"{data_array.name} ({data_array.units if hasattr(data_array, 'units') else ''})")
        plt.grid(True)

    def calculate_statistics(self, data_array=None, variable_name=None):
        """
        计算数据的基本统计信息

        参数:
            data_array (xarray.DataArray, 可选): 要分析的数据数组，如果为None，则使用variable_name从dataset中提取
            variable_name (str, 可选): 要从dataset中提取的变量名称，仅当data_array为None时使用

        返回:
            dict: 包含统计信息的字典
        """
        # 如果没有提供data_array，尝试从dataset中提取变量
        if data_array is None:
            if variable_name is not None:
                data_array = self.extract_variable(variable_name)
            elif self.dataset is not None and len(self.dataset.data_vars) > 0:
                # 如果没有指定变量名，使用第一个数据变量
                variable_name = list(self.dataset.data_vars)[0]
                data_array = self.extract_variable(variable_name)
                print(f"使用默认变量: {variable_name}")
            else:
                print("没有可用的数据，请提供data_array或variable_name")
                return None

        if data_array is None:
            print("没有可用的数据")
            return None

        # 调用实现方法计算统计信息
        return self._calculate_statistics_impl(data_array)

    def _calculate_statistics_impl(self, data_array):
        """
        实现统计计算的具体逻辑

        参数:
            data_array (xarray.DataArray): 要分析的数据数组

        返回:
            dict: 包含统计信息的字典
        """
        stats = {
            '最小值': float(data_array.min().values),
            '最大值': float(data_array.max().values),
            '平均值': float(data_array.mean().values),
            '标准差': float(data_array.std().values),
            '中位数': float(data_array.median().values)
        }

        return stats

    def export_to_excel(self, data=None, variable_name=None, output_path=None, sheet_name='ERA5数据', time_index=None,
                        lat=None, lon=None):
        """
        将ERA5数据导出到Excel文件

        参数:
            data (xarray.Dataset 或 xarray.DataArray, 可选): 要导出的数据，如果为None，则使用variable_name从dataset中提取或使用整个dataset
            variable_name (str, 可选): 要从dataset中提取的变量名称，仅当data为None时使用
            output_path (str, 可选): 输出Excel文件的路径，如果为None，则使用默认路径
            sheet_name (str): Excel工作表名称
            time_index (int, 可选): 如果提供，则只导出指定时间索引的数据
            lat (float, 可选): 如果提供，则只导出指定纬度的数据
            lon (float, 可选): 如果提供，则只导出指定经度的数据

        返回:
            bool: 导出成功返回True，否则返回False
        """
        # 如果没有提供data，尝试从dataset中提取变量或使用整个dataset
        if data is None:
            if variable_name is not None:
                data = self.extract_variable(variable_name)
            elif self.dataset is not None:
                data = self.dataset
                print("使用整个数据集进行导出")
            else:
                print("没有可用的数据，请提供data或variable_name，或确保dataset已加载")
                return False

        if data is None:
            print("没有可用的数据")
            return False

        # 如果没有提供output_path，创建默认路径
        if output_path is None:
            if isinstance(data, xr.DataArray) and data.name is not None:
                output_path = f"ERA5_{data.name}_导出.xlsx"
            else:
                output_path = "ERA5_数据导出.xlsx"
            print(f"使用默认输出路径: {output_path}")

        try:
            # 确保输出目录存在
            output_dir = os.path.dirname(output_path)
            if output_dir and not os.path.exists(output_dir):
                os.makedirs(output_dir)

            # 导出数据
            result = self._export_to_excel_impl(data, output_path, sheet_name, time_index, lat, lon)

            if result:
                print(f"数据已成功导出到: {output_path}")
            return result

        except Exception as e:
            print(f"导出数据时出错: {e}")
            return False

    def _export_to_excel_impl(self, data, output_path, sheet_name, time_index, lat, lon):
        """
        实现Excel导出的具体逻辑

        参数:
            data (xarray.Dataset 或 xarray.DataArray): 要导出的数据
            output_path (str): 输出Excel文件的路径
            sheet_name (str): Excel工作表名称
            time_index (int, 可选): 如果提供，则只导出指定时间索引的数据
            lat (float, 可选): 如果提供，则只导出指定纬度的数据
            lon (float, 可选): 如果提供，则只导出指定经度的数据

        返回:
            bool: 导出成功返回True，否则返回False
        """
        # 检查数据类型
        if isinstance(data, xr.Dataset):
            # 如果是数据集，转换为数据框
            print(f"导出数据集，包含变量: {list(data.data_vars.keys())}")

            # 创建一个ExcelWriter对象
            with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
                # 为每个变量创建一个工作表
                for var_name, var_data in data.data_vars.items():
                    df = self._convert_dataarray_to_dataframe(var_data, time_index, lat, lon)
                    if df is not None:
                        df.to_excel(writer, sheet_name=f"{sheet_name}_{var_name}")
                        print(f"变量 '{var_name}' 已导出到工作表 '{sheet_name}_{var_name}'")

        elif isinstance(data, xr.DataArray):
            # 如果是数据数组，直接转换为数据框
            df = self._convert_dataarray_to_dataframe(data, time_index, lat, lon)
            if df is not None:
                # 创建一个ExcelWriter对象
                with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
                    df.to_excel(writer, sheet_name=sheet_name)
                    print(f"数据已导出到工作表 '{sheet_name}'")
        else:
            print(f"不支持的数据类型: {type(data)}")
            return False

        return True

    def _convert_dataarray_to_dataframe(self, data_array, time_index=None, lat=None, lon=None):
        """
        将DataArray转换为DataFrame - 通用实现

        参数:
            data_array (xarray.DataArray): 要转换的数据数组
            time_index (int, 可选): 如果提供，则只导出指定时间索引的数据
            lat (float, 可选): 如果提供，则只导出指定纬度的数据
            lon (float, 可选): 如果提供，则只导出指定经度的数据

        返回:
            pandas.DataFrame: 转换后的数据框
        """
        lat_dim, lon_dim = self.get_spatial_dimensions()

        # 根据参数选择数据子集
        data_subset = data_array

        # 如果指定了时间索引，则选择特定时间点的数据
        if time_index is not None and self.time_dim in data_array.dims:
            if time_index >= len(data_array[self.time_dim]):
                print(f"时间索引超出范围，最大索引为 {len(data_array[self.time_dim]) - 1}")
                return None
            data_subset = data_array.isel({self.time_dim: time_index})

        # 如果指定了经纬度，则选择特定位置的数据
        if lat is not None and lon is not None:
            if lat_dim in data_array.dims and lon_dim in data_array.dims:
                data_subset = data_subset.sel({lat_dim: lat, lon_dim: lon}, method='nearest')
                print(f"已选择位置: {lat_dim}={data_subset[lat_dim].values}, {lon_dim}={data_subset[lon_dim].values}")
            else:
                print(f"数据不包含经纬度维度 {lat_dim} 和 {lon_dim}，无法提取特定位置")

        # 转换为DataFrame
        try:
            # 对于多维数据，尝试转换为二维表格形式
            if len(data_subset.dims) > 2:
                print(f"警告: 数据维度为{len(data_subset.dims)}，将尝试转换为二维表格")

                # 调用子类特定的数据转换策略
                df = self._convert_multi_dim_data(data_subset, lat_dim, lon_dim)
            else:
                # 对于一维或二维数据，直接转换
                df = data_subset.to_dataframe().reset_index()

            return df

        except Exception as e:
            print(f"转换数据时出错: {e}")
            return None

    @abstractmethod
    def _convert_multi_dim_data(self, data_subset, lat_dim, lon_dim):
        """
        将多维数据转换为DataFrame的策略方法，由子类实现
        
        参数:
            data_subset (xarray.DataArray): 要转换的数据子集
            lat_dim (str): 纬度维度名称
            lon_dim (str): 经度维度名称
            
        返回:
            pandas.DataFrame: 转换后的数据框
        """
        pass

    @abstractmethod
    def get_spatial_dimensions(self):
        """
        获取空间维度名称

        返回:
            tuple: 包含纬度和经度维度名称的元组
        """
        pass

    def close(self):
        """
        关闭数据集
        """
        if self.dataset is not None:
            self.dataset.close()
            print("数据集已关闭")


class DailyParser(ERA5Parser):
    """
    日降水量数据解析器
    处理具有bday（天数）、lat（纬度）、lon（经度）维度的ERA5数据
    """

    def format_date(self, time_value):
        """
        将bday值格式化为yyyy-mm-dd格式的日期字符串
        
        参数:
            time_value: bday值（一年中的第几天，从0开始）
            
        返回:
            str: 格式化的日期字符串，格式为yyyy-mm-dd
        """
        # 如果没有找到年份，使用当前年份
        year = self.year if self.year is not None else datetime.datetime.now().year

        # 创建该年的1月1日作为基准日期
        base_date = pd.Timestamp(f"{year}-01-01")

        # 将bday值（天数）添加到基准日期
        date = base_date + pd.Timedelta(days=int(time_value))
        return date.strftime('%Y-%m-%d')

    def get_time_dimension(self):
        """
        获取时间维度名称

        返回:
            str: 时间维度的名称
        """
        return 'bday'

    def print_bday_values(self):
        """
        打印bday维度的数据值
        
        此方法提取并显示bday维度的所有值，帮助用户了解数据的时间结构
        """
        if self.dataset is None:
            print("没有可用的数据集")
            return

        if 'bday' not in self.dataset.dims:
            print("数据集中不包含bday维度")
            print(f"可用维度: {list(self.dataset.dims)}")
            return

        bday_values = self.dataset['bday'].values

        print("\nbday维度值:")
        print("=" * 50)
        print(f"类型: {type(bday_values)}")
        print(f"形状: {bday_values.shape}")
        print(f"数据类型: {bday_values.dtype}")

        # 将bday值（0-365）转换为日期格式
        # bday表示一年中的第几天（从0开始）
        # 尝试将bday值转换为日期格式
        try:
            # 使用解析的年份信息或当前年份
            print(f"\n使用数据年份: {self.year}")
            print("\n转换后的日期格式:")
            for i, days in enumerate(bday_values):
                # 使用format_date方法格式化日期
                date_str = self.format_date(days)
                print(f"原始bday值 {i}: {days} -> {date_str}")
        except Exception as e:
            print(f"\n注意: 无法将bday值转换为日期格式: {e}")

    def get_spatial_dimensions(self):
        """
        获取空间维度名称

        返回:
            tuple: 包含纬度和经度维度名称的元组
        """
        return 'lat', 'lon'

    def _prepare_time_slice(self, data_array, time_index):
        """
        准备时间切片数据 - 日降水量数据专用实现

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            time_index (int): 要处理的时间索引

        返回:
            tuple: (data_slice, time_str) 数据切片和时间字符串
        """
        # 检查数据数组是否包含时间维度
        if self.time_dim in data_array.dims:
            # 选择特定时间点的数据
            if time_index >= len(data_array[self.time_dim]):
                print(f"时间索引超出范围，最大索引为 {len(data_array[self.time_dim]) - 1}")
                time_index = 0

            time_value = data_array[self.time_dim].values[time_index]
            data_slice = data_array.isel({self.time_dim: time_index})

            # 使用format_date方法将时间值转换为日期字符串
            try:
                time_str = self.format_date(time_value)
            except Exception as e:
                print(f"警告: 无法格式化日期: {e}")
                time_str = f"{self.time_dim}={time_value}"
        else:
            data_slice = data_array
            time_str = ""

        return data_slice, time_str

    def _prepare_time_series_data(self, data_array, lat=None, lon=None):
        """
        准备时间序列数据 - 日降水量数据专用实现

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            lat (float): 要提取的纬度点
            lon (float): 要提取的经度点

        返回:
            tuple: (data_point, location_str) 数据点和位置字符串
        """
        # 调用父类方法获取基本的时间序列数据
        data_point, location_str = super()._prepare_time_series_data(data_array, lat, lon)

        # 这里可以添加日降水量数据特有的处理逻辑

        return data_point, location_str

    def _convert_multi_dim_data(self, data_subset, lat_dim, lon_dim):
        """
        将多维数据转换为DataFrame - 日降水量数据专用实现
        
        参数:
            data_subset (xarray.DataArray): 要转换的数据子集
            lat_dim (str): 纬度维度名称
            lon_dim (str): 经度维度名称
            
        返回:
            pandas.DataFrame: 转换后的数据框
        """
        # 日降水量数据优先使用 bday 作为索引
        if self.time_dim in data_subset.dims:
            # 将时间作为行索引，其他维度作为列
            other_dims = [dim for dim in data_subset.dims if dim != self.time_dim]
            if other_dims:
                df = data_subset.to_dataframe().unstack(other_dims)
                print(f"已将数据转换为以{self.time_dim}为索引的表格")
            else:
                df = data_subset.to_dataframe()
        # 如果有经纬度维度，使用它们作为索引
        elif lat_dim in data_subset.dims and lon_dim in data_subset.dims:
            # 将经纬度作为行索引，其他维度作为列
            df = data_subset.to_dataframe().unstack(lon_dim)
            # 重命名列，使其更易读
            df.columns = [f"{lon_dim}_{lon}" for lon in data_subset[lon_dim].values]
            print(f"已将数据转换为以{lat_dim}为索引，{lon_dim}为列的表格")
        else:
            # 如果没有特定的维度结构，直接转换为扁平的DataFrame
            df = data_subset.to_dataframe().reset_index()
            print("已将数据转换为扁平表格结构")

        return df


class HourlyParser(ERA5Parser):
    """
    小时降水量数据解析器
    处理具有time（时间）、latitude（纬度）、longitude（经度）维度的ERA5数据
    """

    def format_date(self, time_value):
        """
        将time值格式化为yyyy-mm-dd HH:00:00格式的日期时间字符串
        
        参数:
            time_value: time值
            
        返回:
            str: 格式化的日期时间字符串，格式为yyyy-mm-dd HH:00:00
        """
        import pandas as pd
        import datetime

        # 如果没有找到年份和月份，使用当前年月
        year = self.year if self.year is not None else datetime.datetime.now().year
        month = self.month if self.month is not None else datetime.datetime.now().month

        try:
            # 尝试将time_value转换为datetime对象
            dt = pd.to_datetime(time_value)
            # 如果time_value已经包含年月日时分秒信息，直接格式化
            return dt.strftime('%Y-%m-%d %H:00:00')
        except:
            # 如果转换失败，假设time_value是小时数
            # 创建该年月的1日作为基准日期
            base_date = pd.Timestamp(f"{year}-{month:02d}-01")

            # 将time_value（小时数）添加到基准日期
            date = base_date + pd.Timedelta(hours=int(time_value))
            return date.strftime('%Y-%m-%d %H:00:00')

    def get_time_dimension(self):
        """
        获取时间维度名称

        返回:
            str: 时间维度的名称
        """
        return 'time'

    def get_spatial_dimensions(self):
        """
        获取空间维度名称

        返回:
            tuple: 包含纬度和经度维度名称的元组
        """
        return 'latitude', 'longitude'

    def _prepare_time_slice(self, data_array, time_index):
        """
        准备时间切片数据 - 小时降水量数据专用实现

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            time_index (int): 要处理的时间索引

        返回:
            tuple: (data_slice, time_str) 数据切片和时间字符串
        """
        # 检查数据数组是否包含时间维度
        if self.time_dim in data_array.dims:
            # 选择特定时间点的数据
            if time_index >= len(data_array[self.time_dim]):
                print(f"时间索引超出范围，最大索引为 {len(data_array[self.time_dim]) - 1}")
                time_index = 0

            time_value = data_array[self.time_dim].values[time_index]
            data_slice = data_array.isel({self.time_dim: time_index})

            # 使用format_date方法将时间值转换为日期时间字符串
            try:
                time_str = self.format_date(time_value)
            except Exception as e:
                print(f"警告: 无法格式化日期时间: {e}")
                time_str = f"{self.time_dim}={time_value}"
        else:
            data_slice = data_array
            time_str = ""

        return data_slice, time_str

    def _prepare_time_series_data(self, data_array, lat=None, lon=None):
        """
        准备时间序列数据 - 小时降水量数据专用实现

        参数:
            data_array (xarray.DataArray): 要处理的数据数组
            lat (float): 要提取的纬度点
            lon (float): 要提取的经度点

        返回:
            tuple: (data_point, location_str) 数据点和位置字符串
        """
        # 调用父类方法获取基本的时间序列数据
        data_point, location_str = super()._prepare_time_series_data(data_array, lat, lon)

        # 这里可以添加小时降水量数据特有的处理逻辑

        return data_point, location_str

    def _convert_multi_dim_data(self, data_subset, lat_dim, lon_dim):
        """
        将多维数据转换为DataFrame - 小时降水量数据专用实现
        
        参数:
            data_subset (xarray.DataArray): 要转换的数据子集
            lat_dim (str): 纬度维度名称
            lon_dim (str): 经度维度名称
            
        返回:
            pandas.DataFrame: 转换后的数据框
        """
        # 小时降水量数据优先使用time作为索引
        if self.time_dim in data_subset.dims:
            # 将时间作为行索引，其他维度作为列
            other_dims = [dim for dim in data_subset.dims if dim != self.time_dim]
            if other_dims:
                df = data_subset.to_dataframe().unstack(other_dims)
                print(f"已将数据转换为以{self.time_dim}为索引的表格")
            else:
                df = data_subset.to_dataframe()
        # 如果有经纬度维度，使用它们作为索引
        elif lat_dim in data_subset.dims and lon_dim in data_subset.dims:
            # 将经纬度作为行索引，其他维度作为列
            df = data_subset.to_dataframe().unstack(lon_dim)
            # 重命名列，使其更易读
            df.columns = [f"{lon_dim}_{lon}" for lon in data_subset[lon_dim].values]
            print(f"已将数据转换为以{lat_dim}为索引，{lon_dim}为列的表格")
        else:
            # 如果没有特定的维度结构，直接转换为扁平的DataFrame
            df = data_subset.to_dataframe().reset_index()
            print("已将数据转换为扁平表格结构")

        return df


def create_parser(file_path):
    """
    根据文件路径创建适当的解析器

    参数:
        file_path (str): NetCDF文件的路径

    返回:
        ERA5Parser: 适合该数据类型的解析器实例
    """
    try:
        # 使用xarray打开NetCDF文件
        ds = xr.open_dataset(file_path)
        print(f"成功加载文件: {os.path.basename(file_path)}")

        # 检查数据维度以确定解析器类型
        if 'bday' in ds.dims:
            print("检测到日降水量数据格式 (bday, latitude, longitude)")
            return DailyParser(ds, file_path)
        elif 'time' in ds.dims:
            print("检测到小时降水量数据格式 (time, latitude, longitude)")
            return HourlyParser(ds, file_path)
        else:
            print("警告: 无法确定数据类型，将使用默认解析器")
            # 如果无法确定类型，返回None
            ds.close()
            return None
    except Exception as e:
        print(f"加载文件时出错: {e}")
        return None


def main():
    """
    主函数，演示如何使用上述函数解析和分析ERA5数据
    """
    # 示例文件路径 - 用户需要修改为实际文件路径
    # 日降水量数据示例
    daily_file_path = "../Daily_ERA5(1960-2024)/ERA5.P.daily.1960.nc"
    # 小时降水量数据示例
    hourly_file_path = "ERA5-P.hourly.1940.09.nc"

    # 选择要处理的文件
    file_path = daily_file_path  # 可以修改为hourly_file_path进行测试

    # 1. 创建适当的解析器
    print("\n1. 创建ERA5数据解析器...")
    parser = create_parser(file_path)

    if parser is None:
        print("无法创建解析器，程序退出")
        return

    # 2. 显示数据集信息
    print("\n2. 显示数据集基本信息...")
    parser.display_info()

    # 2.1 如果是DailyParser，打印bday维度的值
    if isinstance(parser, DailyParser):
        print("\n2.1 打印bday维度的值...")
        parser.print_bday_values()

    # 3. 提取变量
    print("\n3. 提取主要变量...")
    main_var_name = list(parser.dataset.data_vars)[0]  # 获取第一个数据变量
    print(f"提取变量: {main_var_name}, {parser.dataset.data_vars}")
    # 注意：不再需要显式提取变量，后续方法会自动处理

    # 4. 计算并显示基本统计信息
    print("\n4. 计算基本统计信息...")
    # 使用简化的API，不需要传递变量数据
    stats = parser.calculate_statistics(variable_name=main_var_name)
    if stats:
        print("统计信息:")
        for stat_name, stat_value in stats.items():
            print(f"  - {stat_name}: {stat_value}")

    # 5. 绘制空间分布图（第一个时间点）
    print("\n5. 绘制空间分布图...")
    print("注意: 如果在没有GUI的环境中运行，图形将不会显示")
    print("提示: 取消下面的注释以显示图形")
    # 使用简化的API，直接传递变量名
    # parser.plot_spatial_data(variable_name=main_var_name, time_index=0,
    #                        title=f"{main_var_name} 空间分布")

    # 6. 绘制时间序列（全球平均）
    print("\n6. 绘制时间序列...")
    print("注意: 如果在没有GUI的环境中运行，图形将不会显示")
    print("提示: 取消下面的注释以显示图形")
    # 使用简化的API，直接传递变量名
    parser.plot_time_series(variable_name=main_var_name)

    # 7. 导出数据到Excel
    print("\n7. 导出数据到Excel...")
    # 获取空间维度名称
    lat_dim, lon_dim = parser.get_spatial_dimensions()
    if all(dim in parser.dataset[main_var_name].dims for dim in [lat_dim, lon_dim]):
        # 选择一个示例位置（这里使用数据的中间位置作为示例）
        sample_lat = float(
            parser.dataset[main_var_name][lat_dim].values[len(parser.dataset[main_var_name][lat_dim]) // 2])
        sample_lon = float(
            parser.dataset[main_var_name][lon_dim].values[len(parser.dataset[main_var_name][lon_dim]) // 2])
        print(f"\n导出位置({lat_dim}:{sample_lat}, {lon_dim}:{sample_lon})的数据")
        # 使用简化的API，直接传递变量名
        parser.export_to_excel(variable_name=main_var_name,
                               sheet_name=f'{main_var_name}位置数据',
                               lat=sample_lat, lon=sample_lon)
    # 8. 关闭数据集
    print("\n8. 关闭数据集...")
    parser.close()


if __name__ == "__main__":
    print("ERA5 NetCDF数据解析示例")
    print("=" * 50)
    main()
    print("\n程序执行完毕")
